/*
 * This file is part of Catfish.
 * Copyright (C) 2010 Namazu Studios LLC
 * 
 * Catfish is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of 
 * the License, or (at your option) any later version.
 * 
 * Catfish is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with Catfish.  If not, please visit:
 *  
 * http://www.gnu.org/licenses/
 *  
 */
package com.namazustudios.catfish;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.inject.AbstractModule;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.MapBinder;
import com.google.inject.multibindings.Multibinder;
import com.namazustudios.catfish.callback.Delete;
import com.namazustudios.catfish.callback.Get;
import com.namazustudios.catfish.callback.Put;
import com.namazustudios.catfish.converter.Converter;
import com.namazustudios.catfish.converter.EnumConverter;
import com.namazustudios.catfish.converter.ObjectSerializer;
import com.namazustudios.catfish.converter.WeakReferenceConverter;
import com.namazustudios.catfish.datasource.DirectDataSource;
import com.namazustudios.catfish.gae.GaeCatfish;
import com.namazustudios.catfish.gae.GaeDatastoreProvider;
import com.namazustudios.catfish.gae.GaeMarshaller;
import com.namazustudios.catfish.gae.GaeMemcacheProvider;
import com.namazustudios.catfish.gae.GaeUnmarshaller;
import com.namazustudios.catfish.generator.IdentityGenerator;
import com.namazustudios.catfish.generator.IncrementalIdentityGenerator;
import com.namazustudios.catfish.generator.RandomIdentityGenerator;
import com.namazustudios.catfish.provider.DefaultValueProvider;
import com.namazustudios.catfish.provider.EnumValueProvider;
import com.namazustudios.catfish.provider.ValueProvider;
import com.namazustudios.catfish.validator.Validator;

/**
 * Bootstraps the Catfish library providing the absolute minimum configuration
 * necessary to perform basic operations. You may use this for standalone
 * operation, or as a template to use in your own Guicy application.
 * 
 * @author patricktwohig
 * 
 */
public class CatfishModule extends AbstractModule {

    private MapBinder<Class<?>, Put<?>> puts;

    private MapBinder<Class<?>, Get<?>> gets;

    private MapBinder<Class<?>, Delete<?>> deletes;

    private Multibinder<Class<?>> types;

    private MapBinder<String, ValueProvider<?>> valueProviders;

    private MapBinder<String, IdentityGenerator<?>> identityGenerators;

    private MapBinder<String, Validator<?>> validators;

    private MapBinder<String, Converter<?, ?>> converters;

    /**
     * Bootstraps the library with the basic functionality necessary for
     * operation. Subclasses must call this method to make use of the library.
     * 
     * This sets up the following:
     * <ul>
     * <li>A ValueProvider named "enum" which provides Enum values</li>
     * <li>A ValueProvider named "default" which provides default vaules for
     * every field</li>
     * <li>An IdentiyGenerator named "random" which uses random values for
     * identifiers</li>
     * <li>An IdentiyGenerator named "incremental" which incrementally assigns
     * values</li>
     * <li>A Converter named "enum" for Enum types</li>
     * <li>A Converter named "weak" for WeakReference<?> types to strings</li>
     * <li>A Converter named "serialize" which serializes instances of
     * Serializable to Blobs</li>
     * </ul>
     */
    @Override
    protected void configure() {

        bind(Marshaller.class).to(GaeMarshaller.class);
        bind(Unmarshaller.class).to(GaeUnmarshaller.class);
        bind(Catfish.class).to(GaeCatfish.class).in(Scopes.SINGLETON);
        bind(DataSource.class).to(DirectDataSource.class).in(Scopes.SINGLETON);
        bind(MemcacheService.class).toProvider(GaeMemcacheProvider.class).in(Scopes.SINGLETON);
        bind(DatastoreService.class).toProvider(GaeDatastoreProvider.class).in(Scopes.SINGLETON);

        valueProviders().addBinding("enum").to(EnumValueProvider.class);
        valueProviders().addBinding("default").to(DefaultValueProvider.class);

        identityGenerators().addBinding("random").to(RandomIdentityGenerator.class);
        identityGenerators().addBinding("incremental").to(IncrementalIdentityGenerator.class);

        converters().addBinding("enum").to((Class<? extends Converter<?, ?>>) EnumConverter.class);
        converters().addBinding("weak").to((Class<? extends Converter<?, ?>>) WeakReferenceConverter.class);
        converters().addBinding("serialize").to((Class<? extends Converter<?, ?>>) ObjectSerializer.class);

    }

    /**
     * A mapbinder which binds types to delete listeners. This associates
     * persistent types to Delete listeners.
     * 
     * @return a MapBinder instance
     */
    protected MapBinder<Class<?>, Delete<?>> deletes() {

        if (deletes == null) {
            deletes = MapBinder.newMapBinder(binder(), new TypeLiteral<Class<?>>() {}, new TypeLiteral<Delete<?>>() {});
        }

        return deletes;
    }

    /**
     * A mapbinder which binds types to get listeners. This associates
     * persistent types to Delete listeners.
     * 
     * @return a MapBinder instance
     */
    protected MapBinder<Class<?>, Get<?>> gets() {

        if (gets == null) {
            gets = MapBinder.newMapBinder(binder(), new TypeLiteral<Class<?>>() {}, new TypeLiteral<Get<?>>() {});
        }

        return gets;
    }

    /**
     * A mapbinder which binds types to put listeners. This associates 
     * persistent types to Delete listeners.
     * 
     * @return a MapBinder instance
     */
    protected MapBinder<Class<?>, Put<?>> puts() {

        if (puts == null) {
            puts = MapBinder.newMapBinder(binder(), new TypeLiteral<Class<?>>() {}, new TypeLiteral<Put<?>>() {});
        }

        return puts;
    }

    /**
     * A multibinder which provides a list of known Types annotated with the
     * @Entity annotation.  When unmarashalling objects, Catfish needs to
     * associate Kinds with Classes so it can instantiate them when fetched
     * from the datastore.
     * 
     * @return a MapBinder instance
     */
    protected Multibinder<Class<?>> types() {

        if (types == null) {
            types = Multibinder.newSetBinder(binder(), new TypeLiteral<Class<?>>() {});
        }

        return types;
    }

    /**
     * A MapBinder which associates names of VaalueProviders used when
     * unmarshalling data  from the datastore.  Once named, ValueProviders
     * can be associated with the appropraite fields on the Annotation types.
     * 
     * @return a MapBinder instance
     */
    protected MapBinder<String, ValueProvider<?>> valueProviders() {

        if (valueProviders == null) {
            valueProviders = MapBinder.newMapBinder(binder(), new TypeLiteral<String>() {},
                    new TypeLiteral<ValueProvider<?>>() {});
        }

        return valueProviders;
    }

    /**
     * A MapBinder which associates names of IdentityGenerators used when
     * unmarshalling data  from the datastore.  Once named, IdentityGenerators
     * can be associated with the appropraite fields on the Annotation types.
     * 
     * @return a MapBinder instance
     */
    protected MapBinder<String, IdentityGenerator<?>> identityGenerators() {

        if (identityGenerators == null) {
            identityGenerators = MapBinder.newMapBinder(binder(), new TypeLiteral<String>() {},
                    new TypeLiteral<IdentityGenerator<?>>() {});
        }

        return identityGenerators;
    }

   /**
    * A MapBinder which associates names of Validators used when
    * unmarshalling data  from the datastore.  Once named, Validators
    * can be associated with the appropraite fields on the Annotation types.
    * 
    * @return a MapBinder instance
    */
    protected MapBinder<String, Validator<?>> validators() {

        if (validators == null) {
            validators = MapBinder.newMapBinder(binder(), new TypeLiteral<String>() {},
                    new TypeLiteral<Validator<?>>() {});
        }

        return validators;
    }

    /**
     * A MapBinder which associates names of Converters used when
     * unmarshalling data  from the datastore.  Once named, Converters
     * can be associated with the appropraite fields on the Annotation types.
     * 
     * @return a MapBinder instance
     */
    protected MapBinder<String, Converter<?, ?>> converters() {

        if (converters == null) {
            converters = MapBinder.newMapBinder(binder(), new TypeLiteral<String>() {},
                    new TypeLiteral<Converter<?, ?>>() {});
        }

        return converters;
    }

}
